-
Notifications
You must be signed in to change notification settings - Fork 298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove extra SHL/SHR CTL. #1270
Remove extra SHL/SHR CTL. #1270
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this is a nice improvement, thanks! There are several things that will need fixing or tidying up though; see my comments.
evm/src/arithmetic/mod.rs
Outdated
let shifted_input1 = if input1.bits() <= 32 { | ||
U256::one() << input1 | ||
} else { | ||
U256::zero() | ||
}; | ||
input0.overflowing_mul(shifted_input1).0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, are you sure this is right? Maybe I'm missing something, but I would have expected this to be
let shifted_input1 = if input1.bits() <= 32 { | |
U256::one() << input1 | |
} else { | |
U256::zero() | |
}; | |
input0.overflowing_mul(shifted_input1).0 | |
if input0 < U256::from(256usize) { | |
input1 << input0 | |
} else { | |
U256::zero() | |
} |
as in
plonky2/evm/src/cpu/kernel/interpreter.rs
Line 673 in 51eb7c0
fn run_shl(&mut self) { |
If your implementation is correct, could you please document (i) why the argument order is reversed (cf. https://www.evm.codes/#1b?fork=shanghai) and (ii) why you limit input1
to 2^32 rather than 256? Thanks!
On the other hand, if my suggestion is correct, then we should probably expand the test suite since it would be a bit concerning that this wasn't caught!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The argument order for the generation on the Arithmetic side was indeed reversed: I fixed this.
I limited input1
to 2^32 bits (mistakenly) basing my code on this piece of code in operation.rs:
if input0.bits() <= 32 {
let (_, read) = mem_read_gp_with_log_and_fill(LOOKUP_CHANNEL, lookup_addr, state, &mut row);
state.traces.push_memory(read);
} else {
// The shift constraints still expect the address to be set, even though no read will occur.
let channel = &mut row.mem_channels[LOOKUP_CHANNEL];
channel.addr_context = F::from_canonical_usize(lookup_addr.context);
channel.addr_segment = F::from_canonical_usize(lookup_addr.segment);
channel.addr_virtual = F::from_canonical_usize(lookup_addr.virt);
}
where we carry out a memory read if the number of bits is less than 32. I changed the limitation to 16 bits in the new version, but I was wondering why there was a difference between the memory reads and the limit in shift
?
evm/src/arithmetic/mod.rs
Outdated
let shifted_input1 = if input1.bits() <= 32 { | ||
U256::one() << input1 | ||
} else { | ||
U256::zero() | ||
}; | ||
if shifted_input1.is_zero() { | ||
U256::zero() | ||
} else { | ||
input0 / shifted_input1 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar concern to BinaryOperator::Shl
above. I would have expected this to be
let shifted_input1 = if input1.bits() <= 32 { | |
U256::one() << input1 | |
} else { | |
U256::zero() | |
}; | |
if shifted_input1.is_zero() { | |
U256::zero() | |
} else { | |
input0 / shifted_input1 | |
} | |
input1 >> input0 |
as in
plonky2/evm/src/cpu/kernel/interpreter.rs
Line 683 in 51eb7c0
fn run_shr(&mut self) { |
Note that, in any case, we can just use the U256
arithmetic operations here rather than mimicking the way the values are verified later (e.g. shifted_input1
is not used elsewhere).
evm/src/arithmetic/shift.rs
Outdated
// The second register holds the input which needs shifting. | ||
u256_to_array(&mut lv[INPUT_REGISTER_1], input); | ||
u256_to_array(&mut lv[OUTPUT_REGISTER], result); | ||
// If `shift > 2^32`, the shifted displacement is set to 0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If `shift > 2^32`, the shifted displacement is set to 0. | |
// If `shift >= 256`, the shifted displacement is set to 0. |
evm/src/arithmetic/shift.rs
Outdated
lv[IS_SHR] = F::ZERO; | ||
|
||
for _i in 0..N_RND_TESTS { | ||
let shift = U256::from(rng.gen::<usize>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If shift
is a random usize
-sized value, then the probability that it is less than 256 is about 2^-56. :)
let shift = U256::from(rng.gen::<usize>()); | |
let shift = U256::from(rng.gen::<u8>()); |
Probably better to have a separate test for big shifts where the expected result is zero.
evm/src/arithmetic/shift.rs
Outdated
U256::from(lv[ai].to_canonical_u64()) + full_input * U256::from(1 << 16); | ||
} | ||
|
||
let output = full_input * shifted; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that making the suggested change of usize
to u8
above triggers an overflow exception on this line. Probably easiest to just do
let output = full_input * shifted; | |
let output = full_input << shift; |
…est. Fix max number of bit shifts. Cleanup.
@unzvfu Thank you for your review! As suggested in one of the comments, I added separate tests for |
Just a side note on my latest commit and comment: the shift issue on main was causing a couple of tests to fail in evm-tests ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, thanks Linda! Just a few little nits about function visibility that will be easy to incorporate (not a big deal, but I think its a good habit to limit the visibility of helper functions).
And nice work catching the bug with big shifts!
evm/src/arithmetic/divmod.rs
Outdated
|
||
/// Verify that num = quo * den + rem and 0 <= rem < den. | ||
fn eval_packed_divmod_helper<P: PackedField>( | ||
pub fn eval_packed_divmod_helper<P: PackedField>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
pub fn eval_packed_divmod_helper<P: PackedField>( | |
pub(crate) fn eval_packed_divmod_helper<P: PackedField>( |
evm/src/arithmetic/mul.rs
Outdated
let input1 = read_value_i64_limbs(lv, INPUT_REGISTER_1); | ||
|
||
/// Given the two limbs of `left_in` and `right_in`, computes `left_in * right_in`. | ||
pub fn generate_mul<F: PrimeField64>(lv: &mut [F], left_in: [i64; 16], right_in: [i64; 16]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
pub fn generate_mul<F: PrimeField64>(lv: &mut [F], left_in: [i64; 16], right_in: [i64; 16]) { | |
pub(crate) fn generate_mul<F: PrimeField64>(lv: &mut [F], left_in: [i64; 16], right_in: [i64; 16]) { |
evm/src/arithmetic/mul.rs
Outdated
eval_packed_generic_mul(lv, is_mul, input0_limbs, input1_limbs, yield_constr); | ||
} | ||
|
||
pub fn eval_ext_mul_circuit<F: RichField + Extendable<D>, const D: usize>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
pub fn eval_ext_mul_circuit<F: RichField + Extendable<D>, const D: usize>( | |
pub(crate) fn eval_ext_mul_circuit<F: RichField + Extendable<D>, const D: usize>( |
evm/src/arithmetic/mul.rs
Outdated
generate_mul(lv, input0, input1); | ||
} | ||
|
||
pub fn eval_packed_generic_mul<P: PackedField>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
pub fn eval_packed_generic_mul<P: PackedField>( | |
pub(crate) fn eval_packed_generic_mul<P: PackedField>( |
evm/src/arithmetic/shift.rs
Outdated
/// The logic is the same as the one for MUL. The only difference is that | ||
/// the inputs are in `INPUT_REGISTER_1` and `INPUT_REGISTER_2` instead of | ||
/// `INPUT_REGISTER_0` and `INPUT_REGISTER_1`. | ||
pub fn eval_packed_shl<P: PackedField>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
pub fn eval_packed_shl<P: PackedField>( | |
fn eval_packed_shl<P: PackedField>( |
evm/src/arithmetic/shift.rs
Outdated
eval_packed_shr(lv, nv, yield_constr); | ||
} | ||
|
||
pub fn eval_ext_circuit_shl<F: RichField + Extendable<D>, const D: usize>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
pub fn eval_ext_circuit_shl<F: RichField + Extendable<D>, const D: usize>( | |
fn eval_ext_circuit_shl<F: RichField + Extendable<D>, const D: usize>( |
evm/src/arithmetic/shift.rs
Outdated
); | ||
} | ||
|
||
pub fn eval_ext_circuit_shr<F: RichField + Extendable<D>, const D: usize>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
pub fn eval_ext_circuit_shr<F: RichField + Extendable<D>, const D: usize>( | |
fn eval_ext_circuit_shr<F: RichField + Extendable<D>, const D: usize>( |
This PR changes the way SHL and SHR are generated so that all the CPU CTLs looking into
ArithmeticStark
have the same form, and we can save one CTL.Here, shift operations are generated on the arithmetic side rather on the CPU side. This leads to specific constraints for the shift operations, since now, the first input is
shift
rather than1 << shift
.The three input registers are used in the Arithmetic STARK: the first is
shift
, the second is the input, and the third is1 << shift
.This way, we can have the same CTL shape for all arithmetic operations, and it removes the special shift CTL.
@unzvfu Would mind looking at this if you have the time?